﻿using System;
using System.Collections.Generic;
using System.Threading;

namespace Tomato.Allure
{
	internal class EngineImpl
	{
		private enum EventID
		{
			AllureEventAdded,
			AllureExceptionAdded,
			ThreadStopped,
			MaximumCount
		}

		private DateTime m_startUpTime;

		private EnvDTE80.DTE2 m_visualStudioInstace = null;

		private Thread m_thread = null;
		private ManualResetEvent[] m_threadEvents = null;
		private bool m_bStopped = false;
		private bool m_bExceptionRaised = false;

		private Queue<AllureEvent> m_allureEventQueue = null;
		private Queue<AllureException> m_allureExceptions = null;

		private List<ServiceImpl> m_services = null;

		public DateTime StartUpTime
		{
			get { return m_startUpTime; }
		}

		public VisualStudioImpl VisualStudio
		{
			get { return new VisualStudioImpl( m_visualStudioInstace ); }
		}

		public EngineImpl( EnvDTE80.DTE2 visualStudioInstance, List<AddOnInfo>.Enumerator addOns )
		{
			m_startUpTime = DateTime.Now;

			m_visualStudioInstace = visualStudioInstance;
			if( m_visualStudioInstace == null )
			{
				throw new ArgumentNullException();
			}

			m_allureEventQueue = new Queue<AllureEvent>();
			m_allureExceptions = new Queue<AllureException>();

			m_services = new List<ServiceImpl>();
			while( addOns.MoveNext() )
			{
				ServiceImpl service = new ServiceImpl( this, addOns.Current );
				m_services.Add( service );
			}

			m_threadEvents = new ManualResetEvent[ (int)( EventID.MaximumCount ) ];
			m_threadEvents[ ( int )( EventID.AllureEventAdded ) ] = new ManualResetEvent( false );
			m_threadEvents[ ( int )( EventID.AllureExceptionAdded ) ] = new ManualResetEvent( false );
			m_threadEvents[ ( int )( EventID.ThreadStopped ) ] = new ManualResetEvent( false );

			m_bStopped = false;
			m_thread = new Thread( new ThreadStart( DoWork ) );
			m_thread.Start();
		}

		public bool QueryAddOnStatus( AddOnInfo addOnInfo )
		{
			if( addOnInfo.Instance != null )
			{
				List<ServiceImpl>.Enumerator it = m_services.GetEnumerator();
				while( it.MoveNext() )
				{
					if( it.Current.AddOnInfo == addOnInfo )
					{
						return addOnInfo.Instance.OnQueryStatus( it.Current );
					}
				}
			}

			return false;
		}

		public bool ExecuteAddOn( AddOnInfo addOnInfo )
		{
			if( addOnInfo.Instance != null )
			{
				List<ServiceImpl>.Enumerator it = m_services.GetEnumerator();
				while( it.MoveNext() )
				{
					if( it.Current.AddOnInfo == addOnInfo )
					{
						try
						{
							return addOnInfo.Instance.OnExecute( it.Current );
						}
						catch( Exception exception )
						{
							AllureException allureException = new AllureException( addOnInfo, exception );
							EnqueueAllureException( allureException );
						}
					}
				}
			}

			return false;
		}

		public void Stop()
		{
			m_bStopped = true;
			m_threadEvents[ ( int )( EventID.ThreadStopped ) ].Set();
		}

		public void EnqueueAllureEvent( AllureEvent allureEvent )
		{
			if( allureEvent != null )
			{
				m_allureEventQueue.Enqueue( allureEvent );
				m_threadEvents[ ( int )( EventID.AllureEventAdded ) ].Set();
			}
		}

		private void EnqueueAllureException( AllureException allureException )
		{
			if( allureException != null )
			{
				m_allureExceptions.Enqueue( allureException );
				m_threadEvents[ ( int )( EventID.AllureExceptionAdded ) ].Set();

				m_bExceptionRaised = true;
			}
		}

		private void DoWork()
		{
			while( !m_bStopped )
			{
				EventID eventID = EventID.MaximumCount;
				try
				{
					eventID = ( EventID )( ManualResetEvent.WaitAny( m_threadEvents ) );
				}
				catch( Exception ) { }

				switch( eventID )
				{
				case EventID.AllureEventAdded: // AllureEvent
					{
						while( !m_bStopped && !m_bExceptionRaised && m_allureEventQueue.Count > 0 )
						{
							AllureEvent allureEvent = m_allureEventQueue.Dequeue();

							List<ServiceImpl>.Enumerator services = m_services.GetEnumerator();
							while( services.MoveNext() )
							{
								try
								{
									services.Current.HandleAllureEvent( allureEvent );
								}
								catch( Exception exception )
								{
									AllureException allureException = new AllureException( services.Current.AddOnInfo, exception );
									EnqueueAllureException( allureException );
								}
							}
						}

						m_threadEvents[ ( int )( EventID.AllureEventAdded ) ].Reset();
					}
					break;

				case EventID.AllureExceptionAdded:
					{
						while( !m_bStopped && m_allureExceptions.Count > 0 )
						{
							AllureException allureException = m_allureExceptions.Dequeue();

							List<ServiceImpl>.Enumerator services = m_services.GetEnumerator();
							while( services.MoveNext() )
							{

								if( allureException.Owner == services.Current.AddOnInfo )
								{
									try
									{
										services.Current.HandleAllureException( allureException );
									}
									catch( Exception )
									{
									}

									break;
								}
							}
						}

						m_bExceptionRaised = false;
						m_threadEvents[ ( int )( EventID.AllureExceptionAdded ) ].Reset();
					}
					break;

				case EventID.ThreadStopped:
					{
						m_threadEvents[ ( int )( EventID.ThreadStopped ) ].Reset();
						m_bStopped = true;
					}
					break;
				}				
			}
		}
	}
}
